package com.edu.dk.security.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.edu.dk.security.Filter;
import com.edu.dk.security.TwoPhaseCommitWebContext;
import com.edu.dk.security.WebContext;
import com.edu.dk.security.util.StringUtils;

/**
 * 完成filter处理的中间类
 * 
 */
public class FilterHandler {

	private static final String SEARCH_STR = "?";
	private List<Filter> filters = new ArrayList<Filter>();
	private FilterChain filterChain;
	private String servletPath;
	private WebContext context;
	private static final String WEB_CONTEXT_OWNER_KEY = "_web_context_owner_"; // 用于标记WEB_CONTEXT的所有者

	public FilterHandler(String servletPath, FilterChain filterChain, WebContext context, List<Filter> filters) {
		this.context = context;
		this.filterChain = filterChain;
		this.servletPath = servletPath;
		this.filters.add(new BasicFilter());
		if (filters != null) {
			for (Filter f : filters) {
				if (f.isMatch(servletPath)) {
					this.filters.add(f);
				}
			}
		}
	}

	public WebContext getContext() {
		return context;
	}

	public void setContext(WebContext context) {
		this.context = context;
	}

	/**
	 * 
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
		WebContext wrapperContext = null;
		try {
			wrapperContext = getWebContext(context, this.filters, request, response);
			// 如果请求已经结束，则不执行进一步的处理。例如，当requestContext已经被重定向了，则立即结束请求的处理。
			if (isRequestFinished(filters.size(), wrapperContext)) {
				return;
			}
			servletPath = getServletPath(wrapperContext.getRequest(), wrapperContext);// 重新获取路径
			// 可能之前被修改过
			giveUpControl(wrapperContext);
		} catch (Exception e) {
			if (wrapperContext == null) {
				wrapperContext = getWebContext(request);
			}
		} finally {
			postProcess(wrapperContext, this.filters);
		}
	}

	private WebContext getWebContext(WebContext context, List<Filter> filters, HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		WebContext webContext = getWebContext(context.getRequest());
		if (webContext == null) {
			webContext = createWrapperContext(context, filters, request, response);
			context.getRequest().setAttribute(WEB_CONTEXT_OWNER_KEY, this);
		}
		return webContext;
	}

	private WebContext getWebContext(HttpServletRequest request) {
		return (WebContext) request.getAttribute(HttpFilter.REQUEST_CONTEXT_KEY);
	}

	private WebContext createWrapperContext(WebContext context, List<Filter> tinyFilters, HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		SimpleWebContext innerWebContext = new SimpleWebContext(context, this, request, response);
		WebContext wrapperedContext = innerWebContext;
		setWebContext(wrapperedContext);
		try {
			for (Filter filter : tinyFilters) {
				wrapperedContext = filter.wrapContext(wrapperedContext);
				if (wrapperedContext != null) {
					filter.preProcess(wrapperedContext);
					setWebContext(wrapperedContext);
				}
			}
		} finally {
			wrapperedContext = getWebContext(request);
			innerWebContext.setTopWebContext(wrapperedContext);
		}
		return wrapperedContext;
	}

	public static void setWebContext(WebContext WebContext) {
		HttpServletRequest request = WebContext.getRequest();
		request.setAttribute(HttpFilter.REQUEST_CONTEXT_KEY, WebContext);
	}

	/**
	 * 
	 * @param request
	 * @param webContext
	 * @return
	 */
	private String getServletPath(HttpServletRequest request, WebContext webContext) {
		String servletPath = (String) request.getAttribute(HttpFilter.DEFAULT_PAGE_KEY);
		if (StringUtils.isBlank(servletPath)) {
			servletPath = request.getServletPath();
			if (StringUtils.isBlank(servletPath)) {
				servletPath = request.getPathInfo();
			}
			if (StringUtils.isBlank(servletPath)) {// 兼容tomcat8的处理情况，如果servletPath为空，设置为"/"
				servletPath = "/";
			}
		}
		return StringUtils.substringBefore(servletPath, SEARCH_STR);
	}

	private void giveUpControl(WebContext wrapperContext) throws IOException, ServletException {
		// 取消contentType的设置
		try {
			wrapperContext.getResponse().setContentType(null);
		} catch (Exception e) {
			// ignored, 有可能有的servlet engine不支持null参数
		}
		filterChain.doFilter(wrapperContext.getRequest(), wrapperContext.getResponse());
	}

	/**
	 * 判断请求是否已经结束。如果请求被重定向了，则表示请求已经结束。
	 * 
	 * @param webContext
	 * @return
	 */
	private boolean isRequestFinished(int filterSize, WebContext webContext) {
		WebContext wrapContext = webContext;
		for (int i = 0; i < filterSize; i++) {
			if (wrapContext != null) {
				if (wrapContext.isRequestFinished()) {
					return true;
				}
				wrapContext = wrapContext.getWrappedWebContext();
			}
		}
		return false;
	}

	private void postProcess(WebContext webContext, List<Filter> tinyFilters) {
		if (webContext == null) {
			return;
		}
		try {
			if (this == webContext.getRequest().getAttribute(WEB_CONTEXT_OWNER_KEY)) {
				webContext.getRequest().removeAttribute(WEB_CONTEXT_OWNER_KEY);
				commitWebRequest(webContext, tinyFilters);
			}
		} catch (Exception e) {
		}

	}

	/**
	 * 提交web请求操作
	 * 
	 * @param webContext
	 * @param tinyFilters
	 * @throws IOException
	 * @throws ServletException
	 */
	private void commitWebRequest(WebContext webContext, List<Filter> tinyFilters)
			throws ServletException, IOException {
		CommitMonitor monitor = getCommitMonitor(webContext);
		synchronized (monitor) {
			if (!monitor.isCommitted()) {
				boolean doCommitHeaders = !monitor.isHeadersCommitted();
				monitor.setCommitted(true);
				HttpServletRequest request = webContext.getRequest();
				int size = tinyFilters.size() - 1;
				WebContext wrapperedContext = webContext;
				for (int i = size; i >= 0; i--) {
					Filter filter = tinyFilters.get(i);
					if (filter != null) {
						if (wrapperedContext instanceof TwoPhaseCommitWebContext && doCommitHeaders) {
							((TwoPhaseCommitWebContext) wrapperedContext).commitHeaders();
						}
						filter.postProcess(wrapperedContext);
					}
					wrapperedContext = wrapperedContext.getWrappedWebContext();

				}
				// 将request和requestContext断开
				removeWebContext(request);
			}
		}
	}

	public static void removeWebContext(HttpServletRequest request) {
		request.removeAttribute(HttpFilter.REQUEST_CONTEXT_KEY);
	}

	public String getServletPath() {
		return servletPath;
	}

	/**
	 * 头部提交，保证内容只提交一次
	 * 
	 * @param wrapperedContext
	 */
	public void commitHeaders(WebContext wrapperedContext) {
		CommitMonitor monitor = getCommitMonitor(wrapperedContext);
		synchronized (monitor) {
			if (!monitor.isHeadersCommitted()) {
				monitor.setHeadersCommitted(true);
				for (WebContext rc = wrapperedContext; rc != null; rc = rc.getWrappedWebContext()) {
					if (rc instanceof TwoPhaseCommitWebContext) {
						TwoPhaseCommitWebContext tpc = (TwoPhaseCommitWebContext) rc;
						tpc.commitHeaders();
					}
				}
			}
		}
	}

	private CommitMonitor getCommitMonitor(WebContext webContext) {
		CommitMonitor monitor = findWebContext(webContext, SimpleWebContext.class);
		return monitor;
	}

	/**
	 * 
	 * 在指定的request context及其级联的request context中找到一个指定类型的request context。
	 * 
	 * @param webContext
	 *            要搜索的request context
	 * @param webContextInterface
	 *            要查找的类
	 * @return <code>WebContext</code>对象，如果没找到，则返回<code>null</code>
	 * 
	 */
	private <R extends WebContext> R findWebContext(WebContext webContext, Class<R> webContextInterface) {
		do {
			if (webContextInterface.isInstance(webContext)) {
				break;
			}
			webContext = webContext.getWrappedWebContext();
		} while (webContext != null);
		return webContextInterface.cast(webContext);
	}

}
